package com.fausto.learnimprint.core.service.impl

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import com.fausto.learnimprint.cache.RedisCache
import com.fausto.learnimprint.cache.RedisKey
import com.fausto.learnimprint.common.constant.JwtConstant
import com.fausto.learnimprint.common.exception.PwdMatchFailException
import com.fausto.learnimprint.common.exception.UserUnavailableException
import com.fausto.learnimprint.core.mapper.RoleMapper
import com.fausto.learnimprint.core.mapper.UserMapper
import com.fausto.learnimprint.core.mapper.UserRoleMapper
import com.fausto.learnimprint.core.model.dto.UserLogin
import com.fausto.learnimprint.core.model.entity.User
import com.fausto.learnimprint.core.service.IUserService
import com.fausto.learnimprint.utils.JwtUtil
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit

/**
 *
 *
 * 服务实现类
 *
 *
 * @author fausto
 * @since 2022-03-28
 */
@Service
class UserService : ServiceImpl<UserMapper, User>(), IUserService, UserDetailsService {

    @Autowired
    private lateinit var userMapper: UserMapper

    @Autowired
    private lateinit var userRoleMapper: UserRoleMapper

    @Autowired
    private lateinit var roleMapper: RoleMapper

    @Autowired
    private lateinit var jwtUtil: JwtUtil

    @Autowired
    lateinit var encoder: BCryptPasswordEncoder

    @Autowired
    lateinit var redisTemplate: RedisTemplate<String, String>
    /**
     *      用户登录
     *@return: (Token, 用户名, 用户角色)
     */

    override fun login(user: UserLogin): Map<String, Any> {
        //根据用户名从数据库中获取用户信息
        val dbUser = findUserByName(user.username)

        //验证口令
        if (!encoder.matches(user.password, dbUser.password)) {
            //口令错误
            throw PwdMatchFailException("密码错误")
        }
        if (0 == dbUser.userState) {
            //用户已被封禁
            throw UserUnavailableException("用户已被封禁")
        }
        //开始签发Token
        val userDetails = this.loadUserByUsername(user.username)
        //生成token
        val token = jwtUtil.generateToken(userDetails)
        //获取权限信息
        val authorities = userDetails.authorities
        //将TOKEN存储至REDIS中
        redisTemplate.opsForValue().set(
            JwtConstant.PREFIX + user.username,
            JwtConstant.PREFIX + token,
            JwtConstant.EXPIRED_TIME.toLong(),
            TimeUnit.MINUTES
        )
        //返回token和权限信息
        return mapOf(
            "token" to JwtConstant.PREFIX + token,
            "roles" to authorities.toList()
        )
    }

    /**
     *      用户登出：   清楚存储在REDIS中的用户登录信息
     */
    override fun logout() {
        val username = findUserFromContext().username
        redisTemplate.delete(JwtConstant.PREFIX + username)
    }

    /**
     *     重写从数据库中获取用户信息的方法(被框架调用)
     */
    @RedisCache(key = RedisKey.USER_KEY)
    override fun loadUserByUsername(username: String): User {
        //从数据库中根据用户名查询用户
        val user = findUserByName(username)
        //获取用户角色信息
        val roles = getRoles(user).map {
            SimpleGrantedAuthority(it)
        }
        //返回用户信息
        return User(
            user.username,
            "***********",
            roles
        )
    }


    /**
     *      从HTTP请求中获取用户信息(不包括用户ID)
     */
    @RedisCache(key = RedisKey.USER_KEY)
    override fun findUserFromContext(): User {
//        val username: String
//        when(val option = jwtUtil.getUsernameFromRequest(request)){
//            //无法获取用户名,即无token,返回游客身份
//            is Option.None -> return User("GUEST", "null", listOf(SimpleGrantedAuthority("GUEST")))
//            //可以获取用户信息
//            is Option.Some -> username = option.unwrap()
//        }
//        return findUserByName(username)
        return getSessionInfo().principal as User
    }

    /**
     *      从数据库中获取当前登录用户信息
     */
    @RedisCache(key = RedisKey.USER_KEY)
    override fun findUserFromDatabase(): User {
        return findUserByName(
            (getSessionInfo().principal as User).username
        )
    }

    /**
     *      获取用户角色信息
     */
    @RedisCache(key = RedisKey.USER_KEY)
    override fun getRoles(user: User): List<String> {
        //游客
        if (user.username == "GUEST") {
            return listOf("GUEST")
        }
        //从数据库中根据用户ID查询用户角色的ID信息 (一个用户对应多个角色)
        val roles = userRoleMapper.selectByMap(mapOf("user_id" to user.userId))
        //存储角色名
        val authorities = ArrayList<String>()
        //遍历所有用户角色ID
        roles.forEach {
            //根据用户角色ID信息获取角色名
            val role = roleMapper.selectById(it.roleId)
            //将角色名存储
            authorities.add(role.roleName)
        }
        return authorities
    }

    /**
     *      查询用户邮箱
     */
    @RedisCache(key = RedisKey.USER_KEY)
    override fun findUserMail(): String {
        val sessionUser = getSessionInfo() as User
        val user = findUserByName(sessionUser.username)
        return user.userMail
    }

    /**
     * 更新用户邮箱
     *
     * @param newMail     新邮箱
     * @param oldMailCode 旧邮箱验证码
     * @param newMailCode 新邮箱验证码
     */
//    @Transactional(rollbackFor = [Exception::class])
//    override fun updateUserMail(newMail: String, oldMailCode: String, newMailCode: String): Boolean {
//        //获取向旧邮箱发出的验证码
//        val userName: String = (getSessionInfo() as User).username
//        val user = findUserByName(userName)
//
//        //与用户输入的旧邮箱验证码进行匹配
//        if (!checkMailCode(user!!.mail!!, oldMailCode)) {
//            throw RuntimeException("旧邮箱无效验证码")
//        }
//
//        //检查新邮箱是否已存在
//        if (userDao.findUserByMail(newMail) != null) {
//            throw RuntimeException("此邮箱已使用")
//        }
//
//
//        //校验新邮箱验证码
//        if (!checkMailCode(newMail, newMailCode)) {
//            throw RuntimeException("新邮箱无效验证码")
//        }
//        user.mail = newMail
//        //更新用户邮箱信息
//        userDao.updateUser(user)
//    }

    /**
     *      通过用户名获取用户信息
     *          + AOP缓存
     */
    @RedisCache(key = RedisKey.USER_KEY)
    private fun findUserByName(username: String): User {
        return userMapper.selectOne(QueryWrapper<User>().apply {
            eq("user_name", username)
        }) ?: throw UsernameNotFoundException("用户名错误或用户不存在")
    }

    /**
     *      获取验证信息
     */
    private fun getSessionInfo(): Authentication{
        return SecurityContextHolder.getContext().authentication
    }
}







